//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.idvi;

import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.Vector;

import ibook.v10.parameter.ParameterApplet;
import ibook.v10.parameter.ParameterProcessor;
import ibook.v10.parameter.ParameterException;
import ibook.v10.idvi.dvi.DVIDocument;
import ibook.v10.idvi.dvi.DVIFormatException;
import ibook.v10.idvi.display.Block;
import ibook.v10.idvi.display.BlockRoot;
import ibook.v10.idvi.display.ColorScheme;
import ibook.v10.idvi.display.ViewPanel;
import ibook.v10.idvi.display.ZoomPanel;
import ibook.v10.idvi.controls.Controls;

public class DVIOnePageApplet extends ParameterApplet implements Runnable, MessageContext, IDVIContext, ShowDocumentContext {
    Controls    controls;

    URL         fontBase;
    int         dpi             = IDVI.kDefaultDPI;
    int         scaleMin        = IDVI.kDefaultScaleMin;
    int         scaleMax        = IDVI.kDefaultScaleMax;
    int         scaleDefault    = IDVI.kDefaultScaleDefault;
    String      scalePrefix     = IDVI.kDefaultScalePrefix;
    int         pageMin         = IDVI.kDefaultPageMin;
    int         pageMax;
    int         pageDefault;
    String      pagePrefix      = IDVI.kDefaultPagePrefix;
    String      index           = IDVI.kDefaultIndex;
    String      prefix          = IDVI.kDefaultPrefix;

    int         scale;
    int         pageNumber;
    String      documentName;
    URL         documentURL;
    int         pageFirst;

    int         leftMarginDelta;
    int         topMarginDelta;
    ColorScheme colorScheme;

    boolean     parametersOK = false;

    public void init( ) {
        super.init( );

        setLayout( new BorderLayout( ));
        setBackground( Color.white );

        try {
            initParameters( );
        } catch( ParameterException e ) {
            System.out.println( "applet " + getClass( ).getName( ) + ": " + e.getMessage( ));
        }
        
    }

    private void initParameters( ) throws ParameterException {
        //fontBase     = parameter.getURL    ( "fontBase"    );
        try{
            fontBase = new URL("http://www.geom.uiuc.edu/java/idvi/wwwlib/pk");
        }catch(Exception mexc){
            mexc.printStackTrace();
        }
        dpi          = parameter.getInteger( "dpi"         , dpi );
        scaleMin     = parameter.getInteger( "scaleMin"    , IDVI.kScaleMin, IDVI.kScaleMax, scaleMin );
        scaleMax     = parameter.getInteger( "scaleMax"    , scaleMin, IDVI.kScaleMax, scaleMax );
        scaleDefault = parameter.getInteger( "scaleDefault", scaleMin, scaleMax, scaleDefault );
        scalePrefix  = parameter.getString ( "scalePrefix" , scalePrefix );
        pageMin      = parameter.getInteger( "pageMin"     , pageMin );
        pageMax      = parameter.getInteger( "pageMax"     , pageMin - 1 );
        pageDefault  = parameter.getInteger( "pageDefault" , pageMin, pageMax, pageMin ); 
        pagePrefix   = parameter.getString ( "pagePrefix"  , pagePrefix );
        prefix       = parameter.getString ( "prefix"      , prefix );
        pagePrefix   = prefix + pagePrefix;

        index = ParameterProcessor.guessIndex( prefix, index );

        index        = parameter.getString ( "index"       , index );

        URL htmlURL = getDocumentBase( );
        pageNumber = ParameterProcessor.guessNumber(
            htmlURL, pagePrefix, pageDefault );

        pageNumber   = parameter.getInteger( "page"        , pageMin, pageMax, pageNumber );

        scale = ParameterProcessor.guessNumber(
            htmlURL, pagePrefix + pageNumber + scalePrefix, scaleDefault );

        scale        = parameter.getInteger( "scale"       , scaleMin, scaleMax, scale );

        documentName = pagePrefix + pageNumber + ".dvi";
        documentURL  = parameter.getURL    ( "document"    , documentName );
        documentName = parameter.getString ( "document"    , documentName );

        pageFirst = ParameterProcessor.guessNumber(
            documentURL, pagePrefix, pageMin );

        pageFirst    = parameter.getInteger( "pageFirst"   , pageMin, pageMax, pageFirst );

        double conversion = ( double ) scale / dpi;
        leftMarginDelta = parameter.getLength( "leftMarginDelta", dpi, conversion, 0 ) / scale;
        topMarginDelta  = parameter.getLength( "topMarginDelta" , dpi, conversion, 0 ) / scale;

        int backgroundColor   = parameter.getColor( "backgroundColor"  , IDVI.kDefaultBackgroundColor   );
        int foregroundColor   = parameter.getColor( "foregroundColor"  , IDVI.kDefaultForegroundColor   );
        int selectionColor    = parameter.getColor( "selectionColor"   , IDVI.kDefaultSelectionColor );
        int linkColor         = parameter.getColor( "linkColor"        , IDVI.kDefaultLinkColor         );
        int selectedLinkColor = parameter.getColor( "selectedLinkColor", IDVI.kDefaultSelectedLinkColor );

        colorScheme = ColorScheme.getColorScheme(
                backgroundColor, foregroundColor, selectionColor, linkColor, selectedLinkColor );

        IDVI.setDebugParameters( parameter );

        parametersOK = true;
    }

    private boolean loaderStarted = false;

    public void start( ) {
        if( parametersOK ) {
            if( controls == null )
                controls = Controls.getControls( );
            else
                controls.show( );

            grabControls( );
            startApplets( );

            // do this here, so that loader thread knows controls != null:

            if( ! loaderStarted ) {
                loaderStarted = true;
                new Thread( this ).start( );
            }
        }
    }

    public void stop( ) {
        if( parametersOK ) {
            stopApplets( );
            controls.removeContext( this );
        }
    }

    public void destroy( ) {
        if( parametersOK && loaderStarted ) {
            destroyApplets( );

            try {
                DVIDocument.putDocument( getDocument( ));
            } catch( DVIFormatException e ) {
            }
        }
    }

    public boolean keyDown( Event event, int key ) {
        boolean used = false;

        switch( key ) {
//          case '?':
//              IDVI.printDebugInformation( );
//              used = true;
//              break;
//          case '/':
//              view.printStructure( );
//              used = true;
//              break;
            default:
                if( controls != null && ( event.target == this || event.target == view )) {
                    grabControls( );
                    used = controls.keyDown( key );
                }
                break;
        }

        return used;
    }

    private ZoomPanel   zoomPanel = null;
    private boolean     dragging = false;

    public boolean mouseDown( Event event, int x, int y ) {
        if( ! dragging ) {
            dragging = true;
            grabControls( );
        }

        int xCenter = ( x - leftMarginDelta ) * scale;
        int yCenter = ( y - topMarginDelta ) * scale;

        if( zoomPanel != null )
            zoomPanel.setCenter( xCenter, yCenter );
        else {
            Block block = root.getBlock( );
            zoomPanel = new ZoomPanel( block, root, colorScheme, 1, xCenter, yCenter, this, this );
            controls.setZoom( zoomPanel );
        }
        
        return true;
    }

    public boolean mouseDrag( Event event, int x, int y ) {
        return dragging && mouseDown( event, x, y );
    }

    public boolean mouseUp( Event event, int x, int y ) {
        dragging = false;
        return true;
    }

    final private static String[ ][ ] parameterInfo = {
        { "fontBase",       "url",      "where to find font files" },
        { "dpi",            "integer",  "dots per inch, before scaling (default 300)" },
        { "scaleMin",       "1-15",     "smallest available scaling factor (default 1)" },
        { "scaleMax",       "1-15",     "largest available scaling factor (default 5)" },
        { "scaleDefault",   "1-15",     "scale to use for index.html (default 4)" },
        { "scalePrefix",    "string",   "for constructing urls (default \"scale\")" },
        { "pageMin",        "integer",  "first page of document (default 1)" },
        { "pageMax",        "integer",  "last page of document (default from dvi file)" },
        { "pageDefault",    "integer",  "page to use for index.html (default pageMin)" },
        { "pagePrefix",     "string",   "for constructing urls (default \"page\")" },
        { "index",          "url",      "url for default page (default \"index.html\")" },
        { "prefix",         "string",   "for constructing urls (default none)" },

        { "scale",          "1-15",     "scaling factor (default from html file name)" },
        { "page",           "integer",  "page to be displayed (default from html file name)" },
        { "document",       "url",      "dvi file to be displayed (default from page number)" },
        { "pageFirst",      "integer",  "first page in dvi file (default from document name)" },

        { "topMargin",      "length",   "absolute top margin" },
        { "leftMargin",     "length",   "absolute left margin" },
        { "topMarginDelta", "length",   "positive or negative change to default margins" },
        { "leftMarginDelta","length",   "positive or negative change to default margins" }
    };

    public String[ ][ ] getParameterInfo( ) {
        return parameterInfo;
    }

    private BlockRoot   root;
    private ViewPanel   view;

    public void run( ) {
        Thread.currentThread( ).setPriority( IDVI.kPriorityStartLoadingDocument );

        DVIDocument document = DVIDocument.getDocument( documentURL, documentName, dpi, fontBase, this );
        setDocument( document );
        if( document == null ) return;

        root = document.getPage( pageNumber - pageFirst );
        if( root == null ) return;

        Block block = root.getBlock( );
        view = new ViewPanel( block, root, colorScheme, scale, leftMarginDelta, topMarginDelta, this, this, true );

        add( "Center", view );
        validate( );

        if( pageMax < pageMin ) {
            setPageMax( document.getPageCount( ) + pageFirst - 1 );

            // ??? This is not the right thing to do if an entirely different document
            // is being viewed when this loader finishes.

            controls.pageChanged( pageNumber, pageNumber == pageMin, pageNumber == pageMax );
        }
    }




    private DVIDocument document;
    private boolean     documentSet = false;

    private synchronized void setDocument( DVIDocument document ) {
        this.document = document;
        documentSet = true;
        notifyAll( );
    }

    private synchronized DVIDocument getDocument( ) throws DVIFormatException {
        if( parametersOK && loaderStarted )
            while( ! documentSet )
                try {
                    wait( );
                } catch( InterruptedException e ) {
                }
        
        if( document == null )
            throw new DVIFormatException( "document not loaded" );

        return document;
    }

    private synchronized void setPageMax( int pageMax ) {
        this.pageMax = pageMax;
        notifyAll( );
    }

    private synchronized int getPageMax( ) {
        while( pageMax < pageMin )
            try {
                wait( );
            } catch( InterruptedException e ) {
            }
        
        return pageMax;
    }




    //  Handling embedded applets:

    private Vector applets = new Vector( );
    private boolean appletsStarted = false;

    private synchronized void addApplet( AppletContainer appletContainer ) {
        applets.addElement( appletContainer );

        if( appletsStarted )
            appletContainer.start( );
    }

    private synchronized void startApplets( ) {
        if( ! appletsStarted ) {
            appletsStarted = true;

            for( int i = 0; i < applets.size( ); ++ i )
                (( AppletContainer ) applets.elementAt( i )).start( );
        }
    }

    private synchronized void stopApplets( ) {
        if( appletsStarted ) {
            appletsStarted = false;

            for( int i = 0; i < applets.size( ); ++ i )
                (( AppletContainer ) applets.elementAt( i )).stop( );
        }
    }

    private synchronized void destroyApplets( ) {
        for( int i = 0; i < applets.size( ); ++ i )
            (( AppletContainer ) applets.elementAt( i )).destroy( );
    }




    //  IDVIContext implementation:

    public ParameterProcessor getParameterProcessor( ) {
        return parameter;
    }

    public URL getDVIDocumentBase( ) {
        return documentURL;
    }

    public void registerAppletContainer( AppletContainer appletContainer ) {
        appletContainer.init( );
        addApplet( appletContainer );
    }




    //  ShowDocumentContext implementation:

    //  showDocument( ) catches several sorts of special url string:
    //
    //  For links that start with '#', we convert internally
    //  into the name of an html file which will show the correct page.
    //
    //  For URL strings that start with "idvi:", processSpecialURL is used to
    //  convert the url into a page or scale change.
    //
    //  For links of the form pagePrefix<num>.dvi#name, we convert
    //  into the form pagePrefix<num>scalePrefix<num>.html#name.

    public void showDocument( String urlString, String target ) {
        if( urlString != null &&
                urlString.length( ) != 0 &&
                ! processSameFileRelativeURL( urlString, target ) &&
                ! processSpecialURL( urlString, target ) &&
                ! processSameDirectoryDVIURL( urlString, target ))
            showDocumentNoTranslation( urlString, target );
    }




    //  MessageContext implementation:

    public void showMessage( String message ) {
        if( controls != null )
            controls.showMessage( message );
        
        System.out.println( message );
    }




    //  The "real" showDocument

    private void showDocumentNoTranslation( String urlString, String target ) {
        URL url = null;

        try {
            url = new URL( documentURL, urlString );
        } catch( MalformedURLException e ) {
        }

        System.out.println( "url = \"" + url + "\"" );

        if( url != null )
            getAppletContext( ).showDocument( url, target );
        else 
            System.err.println( "bad url " + urlString );
    }




    //  processSameFileRelativeURL( ) handles URLs of the form "#name".  In a multiple-page
    //  dvi file, these may refer to a name which is on another page.  We ask the Document
    //  object to find the page number, and go to the appropriate page.

    private boolean processSameFileRelativeURL( String urlString, String target ) {
        boolean result = false;

        if( urlString.charAt( 0 ) == '#' ) {
            try {
                setPageNumber( pageFirst + getDocument( ).getNameBlockPageNumber( urlString.substring( 1 )), target );
            } catch( DVIFormatException e ) {
            }
            
            result = true;
        }

        return result;
    }

    //  Special URL processing.  This handles URL strings of the following form:
    //
    //      idvi:page:<number>          Show the given page of the current document.
    //      idvi:pageoffset:<number>    Show the page at the given offset from the current page.
    //      idvi:pagemin                Show the first page of the current document.
    //      idvi:pagemax                Show the last page of the current document.
    //      idvi:scale:<number>         Show the current page at the given scale factor.
    //      idvi:scaleoffset:<number>   Show the current page at the current scale factor plus the given offset.
    //      idvi:scalemin               Show the current page at the smallest possible scale factor.
    //      idvi:scalemax               Show the current page at the largest possible scale factor.
    //
    //  The right way to implement this is with a small army of helper classes, all derived
    //  from a single Actor class.  We would look up an Actor object in a Hashtable, and tell
    //  it to act.  It would in turn call the appropriate method of this object.

    private boolean processSpecialURL( String urlString, String target ) {
        boolean result = false;

        if( urlString.regionMatches( true, 0, IDVI.kIDVIURLPrefix, 0, IDVI.kIDVIURLPrefix.length( ))) {
            String idviString = urlString.substring( IDVI.kIDVIURLPrefix.length( ));

            if( IDVI.kPageMinURL.equalsIgnoreCase( idviString ))
                setPageNumber( pageMin, target );

            else if( IDVI.kPageMaxURL.equalsIgnoreCase( idviString ))
                setPageNumber( getPageMax( ), target );
            
            else if( IDVI.kScaleMinURL.equalsIgnoreCase( idviString ))
                setScale( scaleMin, target );
            
            else if( IDVI.kScaleMaxURL.equalsIgnoreCase( idviString ))
                setScale( scaleMax, target );
            
            else {
                int colonIndex = idviString.indexOf( ':' );
                if( colonIndex != -1 && colonIndex != idviString.length( ) - 1 ) {
                    String name = idviString.substring( 0, colonIndex + 1 );
                    String numberString = idviString.substring( colonIndex + 1 );

                    try {
                        int number = Integer.parseInt( numberString );

                        if( IDVI.kPageURLPrefix.equalsIgnoreCase( name ))
                            setPageNumber( number, target );
                        
                        else if( IDVI.kPageOffsetURLPrefix.equalsIgnoreCase( name ))
                            setPageNumber( pageNumber + number, target );
                        
                        else if( IDVI.kScaleURLPrefix.equalsIgnoreCase( name ))
                            setScale( number, target );
                        
                        else if( IDVI.kScaleOffsetURLPrefix.equalsIgnoreCase( name ))
                            setScale( scale + number, target );

                    } catch( NumberFormatException e ) {
                    }
                }
            }
            result = true;
        }

        return result;
    }

    //  processSameDirectoryDVIURL( ) deals with references to other dvi files in the
    //  same directory which are named in such a way that they look like other pages
    //  of this document (ie, it deals with the dvi files output by idvi).

    private boolean processSameDirectoryDVIURL( String urlString, String target ) {
        boolean result = false;

        int prefixLength = pagePrefix.length( );

        if( urlString.regionMatches( true, 0, pagePrefix, 0, prefixLength )) {
            int suffixIndex = urlString.toLowerCase( ).indexOf( ".dvi", prefixLength );

            if( suffixIndex >= 0 )
                try {
                    String urlPageString = urlString.substring( prefixLength, suffixIndex );
                    int urlPageNumber = Integer.parseInt( urlPageString );
                    showDocumentNoTranslation( pageName( urlPageNumber, scale ), target );

                    // oops - forgot this at first!
                    result = true;

                    // when we have tables in our html files with the <a name=>
                    // tags in the correct places, then add
                    //
                    //      urlString.substring( suffixIndex + 4 )
                    //
                    // to the pageName( ) string.

                } catch( NumberFormatException e ) {
                }
        }

        return result;
    }




    //  Utility routines used in processing special urls

    private void setScale( int scale, String target ) {
        if( scale < scaleMin )
            scale = scaleMin;

        if( scale > scaleMax )
            scale = scaleMax;
        
        if( scale != this.scale )
            showDocumentNoTranslation( pageName( pageNumber, scale ), target );
    }

    private void setPageNumber( int pageNumber, String target ) {
        if( pageNumber < pageMin )
            pageNumber = pageMin;
        
        if( pageMax >= pageMin )
            pageNumber = Math.min( pageNumber, pageMax );
        else if( pageNumber > this.pageNumber )
            try {
                pageNumber = getDocument( ).clipPageNumber( pageNumber - pageFirst ) + pageFirst;
            } catch( DVIFormatException e ) {
            }
        
        if( pageNumber != this.pageNumber )
            showDocumentNoTranslation( pageName( pageNumber, scale ), target );
    }

    private String pageName( int pageNumber, int scale ) {
        return pageNumber == pageDefault && scale == scaleDefault
                ? index
                : pagePrefix + pageNumber + scalePrefix + scale + ".html";
    }




    private void grabControls( ) {
        controls.setContext( this );
        controls.pageChanged( pageNumber, pageNumber == pageMin, pageNumber == pageMax );
        controls.scaleChanged( scale, scale == scaleMin, scale == scaleMax );
    }
}
